/*
 * @file gst.c GStreamer interface functions.
 *
 * @Copyright (C) 2007 Kris Marsh.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

#include "gst.h"
#define TIME_FORMAT GST_TIME_FORMAT
/*#define TIME_FORMAT "u:%02u:%02u%01u" */ /* BUG: Different time format doesn't seem to work! */

static GMainLoop *loop = NULL;

static GstElement *pipeline = NULL;
static GstElement *filesrc = NULL;
static GstElement *pitch = NULL;
static GstElement *output = NULL;

static gboolean uri_is_loaded = FALSE;
static gchar *song_uri = NULL;
static gboolean is_playing = FALSE;

/* Callback for when decodebin exposes a source pad */
static void
cb_decodebin_connect(GstElement *decodebin, GstPad *pad, gboolean last, gpointer data)
{
	GstPad *audiopad;

	g_debug("called cb_decodebin_connect");

	/* Only link once */
	audiopad = gst_element_get_pad(data, "sink");
	if (GST_PAD_IS_LINKED(audiopad)) {
		g_object_unref(audiopad);
		return;
	}

	if (gst_pad_link(pad, audiopad) != GST_PAD_LINK_OK) {
		g_print("Failed to link elements decodebin-audioconvert\n");
	}
}


/* Sets up a GStreamer pipeline.
 * returns FALSE if failure, TRUE otherwise. */
static gint
gst_load_pipeline() {
	GstElement *decodebin = NULL;
	GstElement *audioconvert = NULL;

	GstElement *audioconvert2 = NULL;

	g_debug("Creating gstreamer pipeline");

	/* Init GStreamer. */
	loop = g_main_loop_new (NULL, FALSE);
	if (!loop)
		return FALSE;

	/* Set up the pipeline. */
	pipeline = gst_pipeline_new("vcddeck");
	filesrc = gst_element_factory_make ("filesrc", "filesrc");
	decodebin = gst_element_factory_make ("decodebin", "decodebin");
	audioconvert = gst_element_factory_make ("audioconvert", "audioconvert");
	pitch = gst_element_factory_make ("speed", "speed");
	audioconvert2 = gst_element_factory_make ("audioconvert", "ac2");
	output = gst_element_factory_make ("alsasink", "alsasink");

	if (!pipeline || !filesrc || !decodebin || !audioconvert || !pitch || !audioconvert2 || !output) {
		g_print("Could not create at least one element in the pipeline");
		g_assert(pipeline);
		g_assert(filesrc);
		g_assert(decodebin);
		g_assert(audioconvert);
		if (!pitch) { g_error("Pitch (soundtouch) gstreamer plugin not installed, you should install gstreamer-plugins-bad"); }
		g_assert(pitch);
		g_assert(audioconvert2);
		if (!output) { g_error("Output (alsasink) gstreamer plugin not installed, you should install gstreamer-plugins-base"); }
		return FALSE;
	}

	/* Put all elements in a bin */
	if (gst_bin_add(GST_BIN(pipeline), filesrc) == FALSE) {
		g_print("Failed to add element filesrc");
		return FALSE;
	}
	if (gst_bin_add(GST_BIN(pipeline), decodebin) == FALSE) {
		g_print("Failed to add element decodebin");
		return FALSE;
	}
	if (gst_bin_add(GST_BIN(pipeline), audioconvert) == FALSE) {
		g_print("Failed to add element audioconvert");
		return FALSE;
	}
	if (gst_bin_add(GST_BIN(pipeline), pitch) == FALSE) {
		g_print("Failed to add element pitch");
		return FALSE;
	}
	if (gst_bin_add(GST_BIN(pipeline), audioconvert2) == FALSE) {
		g_print("Failed to add element audioconvert2");
		return FALSE;
	}
	if (gst_bin_add(GST_BIN(pipeline), output) == FALSE) {
		g_print("Failed to add element output");
		return FALSE;
	}
	/*gst_bin_add_many(GST_BIN(pipeline),
		filesrc, decodebin, audioconvert, output, NULL);*/
		/*filesrc, decodebin, audioconvert, pitch, audioconvert2, output, NULL);*/

	/* Link elements together. */
	if (gst_element_link(filesrc, decodebin) == FALSE) {
		g_print("Failed to link elements filesrc-decodebin");
		return FALSE;
	}
	/* Decodebin uses dynamic pads, so lets set up a callback. */
	g_signal_connect(decodebin, "new-decoded-pad", G_CALLBACK(cb_decodebin_connect), audioconvert);

	if (!gst_element_link_many(audioconvert, pitch, audioconvert2, output, NULL)) {
		g_print("Failed to link elements audioconvert-pitch-audioconvert-output");
		return FALSE;
	}

	is_playing = FALSE;
	uri_is_loaded = FALSE;

	return TRUE;
}

/* Destroy GStreamer pipeline. */
void
gst_destroy_pipeline() {
	g_debug("Destroying gstreamer pipeline");
	g_free(song_uri);

	/* Cleanup our pipeline. */
	if (pipeline) {
		gst_element_set_state(pipeline, GST_STATE_NULL);
		gst_object_unref (GST_OBJECT(pipeline));
		pipeline = NULL;
	}

	if (loop)
		g_main_loop_quit(loop);
}


/* Loads a URI.
 * returns FALSE if failure, TRUE otherwise. */
/* BUG: When uri is invalid, gst_load_uri should print a warn and return FALSE. Currently it does not. */
gint
gst_load_uri(gchar *uri) {
	/* Load pipeline, if it's not already been loaded. */
	if (!pipeline) {
		g_return_val_if_fail(gst_load_pipeline(), FALSE);
	}

	g_assert(pipeline);

	/* Get real filename from the URI (supplied by e.g. Nautilus). */
	song_uri = g_filename_from_uri(uri, NULL, NULL);

	/* If song_uri is null, then conversion failed. We may just be a local path so give it a bash. */
	if (!song_uri) { 
		song_uri = g_strdup(uri);
	}

	g_debug("Setting URI: '%s'", song_uri);

	g_object_set(G_OBJECT(filesrc), "location", song_uri, NULL);

	uri_is_loaded = TRUE;

	return TRUE;
}

/* Returns whether an URI is loaded or not.
 */
gboolean
gst_uri_is_loaded()
{
	return uri_is_loaded;
}


/* Get the current play position.
 * Return "123 / 123", or NULL if no time/error. */
gchar *
gst_get_play_pos ()
{
	GstFormat fmt = GST_FORMAT_TIME;
	gint64 pos, len;

 	if (!pipeline)
		return NULL;

	/* BUG: Do we need to lock on pipeline? */
	if (!(gst_element_query_position (pipeline, &fmt, &pos)
	&& gst_element_query_duration (pipeline, &fmt, &len))) {
		pos = 0;
	}

	return g_strdup_printf("Track: %s\nTime: %" TIME_FORMAT " / %" TIME_FORMAT,
		song_uri, GST_TIME_ARGS(pos), GST_TIME_ARGS(len));
}


/* Play the GStreamer pipeline.
 * return FALSE if failure, TRUE otherwise.
 */
gint
gst_play() {
	if (!pipeline)
		return FALSE;

	gst_element_set_state (pipeline, GST_STATE_PLAYING);
	is_playing = TRUE;

	g_main_loop_run(loop); /* BUG: Where to put this, it's blocking stuff? */

	return TRUE;
}

/* Pause the GStreamer pipeline.
 * return FALSE if failure, TRUE otherwise.
 */
gint
gst_pause() {
	if (!pipeline)
		return FALSE;

	gst_element_set_state (pipeline, GST_STATE_PAUSED);
	is_playing = FALSE;

	return TRUE;
}

/* Rewind to start of track.
 * return FALSE if failure, TRUE otherwise.
 */
gint
gst_rewind() {
	if (!pipeline)
		return FALSE;

	if (!gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
		GST_SEEK_TYPE_SET, 0,
		GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) {
		g_print("Seek failed!\n");
		return FALSE;
	}
	return TRUE;
}

/* Seek back or forward by amnt.
 * return FALSE if failure, TRUE otherwise;
 */
gint
gst_seek(int amnt) {
	if (!pipeline)
		return FALSE;

	if (!gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH/* GST_SEEK_FLAG_KEY_UNIT */,
		GST_SEEK_TYPE_CUR, 1,
		GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) {
		g_print("Seek failed!\n");
		return FALSE;
	}
	return TRUE;
}

/* Change the pitch of a track.
 * return FALSE if failure, TRUE otherwise.
 */
gint
gst_change_pitch(gdouble new_pitch) {
	gfloat speed = 1 * (100+new_pitch)/100; /* Convert percentage to number between 0-2.0 */

	g_debug("Changing pitch to %f (%f)", new_pitch, speed);
	/*g_object_set(G_OBJECT(pitch), "tempo", speed, NULL);*/
	g_object_set(G_OBJECT(pitch), "speed", speed, NULL);

	return FALSE;
}

/* Returns whether we're currently playing or not.
 */
gboolean
gst_is_playing() {
	return is_playing;
}

/* Set the soundcard to play audio from.
 * return FALSE if failure, TRUE otherwise.
 */
gboolean
gst_set_soundcard(gchar *name) {
	/* Load pipeline, if it's not already been loaded. */
	if (!pipeline) {
		g_return_val_if_fail(gst_load_pipeline(), FALSE);
	}

	g_assert(pipeline);

	g_debug("Setting soundcard to %s", name);
	g_object_set(G_OBJECT(output), "device", name, NULL);

	return TRUE;
}
